#include <stdio.h>
#include <stdlib.h>
#include "lc_array.h"
#include "pac_level.h"
#include "pac_object.h"
#include "pac_ghost.h"

PacLevel* levelCreate() {
    PacLevel *level;

    // Allocation des ressources nécessaires au niveau
    level = (PacLevel*) malloc(sizeof (PacLevel));

    level->ghosts = lcArrayCreate();
    level->map = lcArrayCreate();
    level->pacBalls = lcArrayCreate();
    level->initialPacBalls = lcArrayCreate();

    level->powerball_duration = 10000;
    level->ghost_normal_speed = 1000;
    level->ghost_vulnerable_speed = 1500;
    level->pacman_speed = 100;
    level->remaining_pacBalls = 0;
    level->initialPacBallCount = 0;

    return level;
}

void freeMatrix(LCarray * aMatrix) {
    int i, y;

    LCarray * currentLine;
    // Parcours de toutes les lignes
    for (y = 0; y < lcArrayLength(aMatrix); y++) {
        currentLine = lcArrayGet(aMatrix, y);

        //libération de toutes les lignes
        lcArrayFree(currentLine);
    }

    // Libération de la matrice
    lcArrayFree(aMatrix);

}

void levelFree(PacLevel *level) {
    freeMatrix(level->map);
    freeMatrix(level->pacBalls);
    freeMatrix(level->initialPacBalls);
    lcArrayFree(level->ghosts);

    free(level);
}

void initPacBallsMap(PacLevel * aLevel) {
    // Libération de l'ancienne matrice 
    freeMatrix(aLevel->pacBalls);

    // Récupération de toutes les valeurs initiales des PacBalls
    int x, y;

    LCarray * currentLine;
    LCarray * newLine;

    LCarray* ballMap = lcArrayCreate();

    int currentCase;

    // Parcours de toutes les lignes
    for (y = 0; y < lcArrayLength(aLevel->initialPacBalls); y++) {
        currentLine = lcArrayGet(aLevel->initialPacBalls, y);

        // Création d'une nouvelle ligne
        newLine = lcArrayCreate();

        //Parcours de toutes les valeurs
        for (x = 0; x < lcArrayLength(currentLine); x++) {
            currentCase = lcArrayGet(currentLine, x);

            // libération de toutes les lignes
            lcArrayAdd(newLine, currentCase);
        }

        lcArrayAdd(ballMap, newLine);
    }

    // Réassignation de la map de PacBalls
    aLevel->pacBalls = ballMap;
    aLevel->remaining_pacBalls = aLevel->initialPacBallCount;

}

void levelSetPacmanSpeed(PacLevel *aLevel, int aSpeed) {
    aLevel->pacman_speed = aSpeed;
}

void levelSetGhostNormalSpeed(PacLevel *aLevel, int aSpeed) {
    aLevel->ghost_normal_speed = aSpeed;
}

void levelSetGhostVulnerableSpeed(PacLevel *aLevel, int aSpeed) {
    aLevel->ghost_vulnerable_speed = aSpeed;
}

void levelSetPowerballDuration(PacLevel *aLevel, int aDuration) {
    aLevel->powerball_duration = aDuration;
}

int levelGetSizeY(PacLevel *aLevel) {
    return lcArrayLength(aLevel->map);
}

int levelGetSizeX(PacLevel *aLevel) {
    lcArrayLength(lcArrayGet(aLevel->map, 0));
}

int* levelGetTeleporterTarget(PacLevel *aLevel, int posX, int posY) {
    int teleportTarget[2];

    // Par défaut, la case reste la même
    teleportTarget[0] = posX;
    teleportTarget[1] = posY;

    // Récupération de la valeur de la case courante
    int currentCaseValue = levelGetCaseAt(aLevel, posX, posY);

    // Parcours de toute la map pour trouver la cible du téléporteur
    int x, y;
    int currentCheckedCaseValue;

    for (x = 0; x < levelGetSizeX(aLevel); x++) {
        for (y = 0; y < levelGetSizeY(aLevel); y++) {
            currentCheckedCaseValue = levelGetCaseAt(aLevel, x, y);

            // Si la case possède la meme valeur
            if (currentCheckedCaseValue == currentCaseValue) {

                // Et que ce n'est pas la case sur laquelle l'objet est
                if (x != posX || y != posY) {

                    // Récupération de la position de la case cible de la téléportation
                    teleportTarget[0] = x;
                    teleportTarget[1] = y;

                    // Sortie de la boucle de parcours
                    break;
                }
            }
        }
    }

    return teleportTarget;
}

int levelGetCaseAt(PacLevel *aLevel, int posX, int posY) {
    int levelCase = ERROR_CODE;

    // Si la position de la case est valide.
    if (posX < levelGetSizeX(aLevel) && posY < levelGetSizeY(aLevel) && posX >= 0 && posY >= 0) {

        // Récupération de la ligne courante
        LCarray *currentLine = lcArrayGet(aLevel->map, posY);

        // Récupération de la case voulue
        levelCase = lcArrayGet(currentLine, posX);
    }

    return levelCase;
}

int levelGetPacBallAt(PacLevel *aLevel, int posX, int posY) {
    int pacGum = ERROR_CODE;

    // Si la position de la case est valide.
    if (posX < levelGetSizeX(aLevel) && posY < levelGetSizeY(aLevel) && posX >= 0 && posY >= 0) {

        // Récupération de la ligne de la case
        LCarray *currentLine = lcArrayGet(aLevel->pacBalls, posY);

        // Récupération de la case voulue
        pacGum = lcArrayGet(currentLine, posX);
    }

    return pacGum;
}

void removePacBallAt(PacLevel *aLevel, int posX, int posY) {
    // Si la position de la case est valide.
    if (posX < levelGetSizeX(aLevel) && posY < levelGetSizeY(aLevel) && posX >= 0 && posY >= 0) {

        // Récupération de la ligne de la case
        LCarray *currentLine = lcArrayGet(aLevel->pacBalls, posY);

        // Récupération de la case voulue
        lcArraySet(currentLine, posX, EMPTY_CODE);

        aLevel->remaining_pacBalls--;
    }
}

boolean levelCaseIsAccessibleForPacman(PacLevel * aLevel, int aPosX, int aPosY) {

    int isAccessible = TRUE;

    // Récupération du code de la case voulue
    int caseCode = levelGetCaseAt(aLevel, aPosX, aPosY);

    // Si la case est un mur ou la barriere des fantômes
    if (caseCode == BLOCK_CODE || caseCode == FENCE_CODE || caseCode == ERROR_CODE) {

        // La case n'est pas accessible.
        isAccessible = FALSE;
    }

    return isAccessible;
}

boolean levelCaseIsAccessibleForGhost(PacLevel * aLevel, PacGhost * aGhost, int aPosX, int aPosY) {
    boolean isAccessible = TRUE;

    // Récupération du code de la case voulue
    int caseCode = levelGetCaseAt(aLevel, aPosX, aPosY);

    // Si la case est un mur
    if (caseCode == BLOCK_CODE || caseCode == ERROR_CODE) {

        // La case n'est pas accessible.
        isAccessible = FALSE;
    }

    return isAccessible;
}